#pragma once

// view it on gitee: https://gitee.com/tiger_git/nano_stopwatch

#if _WIN32
#include <windows.h>
#include <Psapi.h>
#include <powrprof.h>
typedef struct _PROCESSOR_POWER_INFORMATION
{
    ULONG Number;
    ULONG MaxMhz;
    ULONG CurrentMhz;
    ULONG MhzLimit;
    ULONG MaxIdleState;
    ULONG CurrentIdleState;
} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;
#ifdef _MSC_VER
#pragma comment(lib, "PowrProf.lib")
#endif
#elif __linux__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#elif __APPLE__
#include <mach/mach_time.h>
#include <unistd.h>
#endif

typedef struct
{
    unsigned long long cpu_cycle;
} nano_stopwatch_t;

static inline double nano_stopwatch_get_cpu_freq_mhz() {
    double mhz = -1;
#if _WIN32
    SYSTEM_INFO si = {0};
    GetSystemInfo(&si);
    PROCESSOR_POWER_INFORMATION ppi[128];
    DWORD dwSize =
        sizeof(PROCESSOR_POWER_INFORMATION) * si.dwNumberOfProcessors;
    memset(&ppi, 0, dwSize);
    LONG ret =
        CallNtPowerInformation(ProcessorInformation, NULL, 0, &ppi[0], dwSize);
    if (ret != 0 || ppi[0].MaxMhz <= 0) {
        return -1;
    }
    mhz = ppi[0].MaxMhz;
#elif __linux__
    FILE* fp = popen(
        "cat /proc/cpuinfo | grep \"cpu.*MHz\" | sed -e 's/.*:[^0-9]//'", "r");
    if (fp) {
        char buf[32];
        if (fgets(buf, sizeof(buf), fp)) {
            mhz = atof(buf);
        }
        fclose(fp);
    }
#elif __APPLE__
    mach_timebase_info_data_t info;
    kern_return_t err = mach_timebase_info(&info);
    if (err == 0) {
        mhz = (double)info.denom / info.numer * 1000;
    }
#endif
    return mhz;
}

static inline unsigned long long nano_stopwatch_get_cpu_cycle() {
#if _WIN32
    return __rdtsc();
#elif __linux__
    unsigned int low, high;
    __asm__ __volatile__("rdtsc" : "=a"(low), "=d"(high));
    return (unsigned long long)high << 32 | low;
#elif __APPLE__
    return mach_absolute_time();
#endif
}

static double nano_stopwatch_cpu_freq_per_ns = -1;

static inline nano_stopwatch_t nano_stopwatch_init() {
    nano_stopwatch_t nsw = {0};
    static unsigned char inited = 0;
    if (inited == 0) {
        nano_stopwatch_cpu_freq_per_ns =
            nano_stopwatch_get_cpu_freq_mhz() / 1000.0;
        inited = 1;
    }
    nsw.cpu_cycle = nano_stopwatch_get_cpu_cycle();
    return nsw;
}

static inline void nano_stopwatch_reset(nano_stopwatch_t* nsw) {
    nsw->cpu_cycle = nano_stopwatch_get_cpu_cycle();
}

static inline double nano_stopwatch_elapsed_ns(nano_stopwatch_t* nsw) {
    return (double)(nano_stopwatch_get_cpu_cycle() - nsw->cpu_cycle) /
           nano_stopwatch_cpu_freq_per_ns;
}

// short name you will like
#define nsw_t nano_stopwatch_t
#define nsw_init() nano_stopwatch_init()
#define nsw_reset(_nsw) nano_stopwatch_reset((_nsw))
#define nsw_elapsed_ns(_nsw) nano_stopwatch_elapsed_ns((_nsw))
#define nsw_elapsed_us(_nsw) (nano_stopwatch_elapsed_ns((_nsw)) / 1000.0)
#define nsw_elapsed_ms(_nsw) (nsw_elapsed_us((_nsw)) / 1000.0)
#define nsw_elapsed_s(_nsw) (nsw_elapsed_ms((_nsw)) / 1000.0)
